from typing import Any
from pathlib import Path

from esmerald import (
    APIView,
    Esmerald,
    Gateway,
    HTTPException,
    Include,
    delete,
    get,
    post,
    put,
)
from esmerald.core.config import StaticFilesConfig
from piccolo.engine import engine_finder
from piccolo.utils.pydantic import create_pydantic_model
from piccolo_admin.endpoints import create_admin

from home.endpoints import home
from home.piccolo_app import APP_CONFIG
from home.tables import Task


async def open_database_connection_pool():
    try:
        engine = engine_finder()
        await engine.start_connection_pool()
    except Exception:
        print("Unable to connect to the database")


async def close_database_connection_pool():
    try:
        engine = engine_finder()
        await engine.close_connection_pool()
    except Exception:
        print("Unable to connect to the database")


TaskModelIn: Any = create_pydantic_model(
    table=Task,
    model_name="TaskModelIn",
)
TaskModelOut: Any = create_pydantic_model(
    table=Task,
    include_default_columns=True,
    model_name="TaskModelOut",
)


# Check if the record is None. Use for query callback
def check_record_not_found(result: dict[str, Any]) -> dict[str, Any]:
    if result is None:
        raise HTTPException(
            detail="Record not found",
            status_code=404,
        )
    return result


class TaskAPIView(APIView):
    path: str = "/"
    tags: list[str] = ["Task"]

    @get("/")
    async def tasks(self) -> list[TaskModelOut]:
        tasks = await Task.select().order_by(Task._meta.primary_key, ascending=False)
        return [TaskModelOut(**task) for task in tasks]

    @get("/{task_id}")
    async def single_task(self, task_id: int) -> TaskModelOut:
        task = (
            await Task.select()
            .where(Task._meta.primary_key == task_id)
            .first()
            .callback(check_record_not_found)
        )
        return TaskModelOut(**task)

    @post("/")
    async def create_task(self, payload: TaskModelIn) -> TaskModelOut:
        task = Task(**payload.model_dump())
        await task.save()
        return TaskModelOut(**task.to_dict())

    @put("/{task_id}")
    async def update_task(self, payload: TaskModelIn, task_id: int) -> TaskModelOut:
        task = (
            await Task.objects()
            .get(Task._meta.primary_key == task_id)
            .callback(check_record_not_found)
        )
        for key, value in payload.model_dump().items():
            setattr(task, key, value)

        await task.save()
        return TaskModelOut(**task.to_dict())

    @delete("/{task_id}")
    async def delete_task(self, task_id: int) -> None:
        task = (
            await Task.objects()
            .get(Task._meta.primary_key == task_id)
            .callback(check_record_not_found)
        )
        await task.remove()


app = Esmerald(
    routes=[
        Gateway("/", handler=home),
        Gateway("/tasks", handler=TaskAPIView),
        Include(
            "/admin/",
            create_admin(
                tables=APP_CONFIG.table_classes,
                # Required when running under HTTPS:
                # allowed_hosts=['my_site.com']
            ),
        ),
    ],
    static_files_config=StaticFilesConfig(path="/static", directory=Path("static")),
    on_startup=[open_database_connection_pool],
    on_shutdown=[close_database_connection_pool],
)
